package game;
import info.*;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.*;

import Missiles.*;

import enemies.BasicEnemy;
import enemies.Boss;
import enemies.StrongEnemy;
import enemies.SuperEnemy;

import java.util.*;
import java.lang.Math;

public class GamePanel extends JPanel implements Constants, Runnable {

	public Shooter shooter;
	public HPBar hpBar;
	public Score score;
	public JButton start;
	private boolean inGame, splash; //splash: whether to show splash screen
	private boolean startGame;  //startGame: whether it is the first time to play
	private boolean bossDefeated;
	private Image image;
	
	public static int countDownNum = 0;
	
	private int rounds; //only after at least 10 rounds (10*DELAY=250 ms) can the player fire another missile
	private Player player;
	private ArrayList<Missile> myMissiles;
	private ArrayList<ShowStatusBonus> bonuses; //indicate the effect of drops
	private ArrayList<Explosion> explosions; 
	/*
	 * currently explosions are added after the boss dies,
	 * but obviously it could be utilized in other situations.
	 */
	private EnemyGenerator enemyGen;

	private Thread shooting, generator;
	
	GamePanel(Shooter parent)    //so that I could use methods and JComponents in the Shooter class
	{  
		inGame = false; splash = true;
		bossDefeated = false; startGame = true;
		score = new Score();
		rounds = 10;
		
		ImageIcon bg = new ImageIcon(this.getClass().getResource(BACKGROUND));
        image = bg.getImage();
        addKeyListener(new myKeyAdapter());
        setFocusable(true);
        
        shooter = parent;
        start = new JButton("Start!");
    	start.addActionListener(new ButtonStart());
    	setLayout(null);
    	add(start);
    	start.setBounds(MAIN_WIDTH / 2 - 40, MAIN_HEIGHT / 2 - 200, 100, 30); //position of button
	}
	
	private void startThread()
	{
		shooting = new Thread(this);
		shooting.start();
	    generator = new Thread(enemyGen);
	    generator.start();
	}
	
    public class ButtonStart implements ActionListener{
    	public void actionPerformed (ActionEvent e){
    		start.setVisible(false);
    		initGameElements();
    		inGame = true; 
    		splash = false;
    		score = new Score();
    		bossDefeated = false;
    		startThread();
    	}
    }
	
	private void initGameElements(){
		player = new Player();
		myMissiles = new ArrayList<Missile>();
		bonuses = new ArrayList<ShowStatusBonus>();
		explosions = new ArrayList<Explosion>();
		enemyGen = new EnemyGenerator(player, explosions);
	}
	
	public void paintComponent(Graphics gs) 
	{
        Graphics2D g = (Graphics2D) gs;
        super.paintComponent(g);
        //draw background
        g.drawImage(image, 0, 0, image.getWidth(null), GAME_HEIGHT, this);
	}
	
	private void paintSplash(Graphics g)
	{
		Image splashImg;
		ImageIcon ii = new ImageIcon(this.getClass().getResource("/image/splash.png"));
        splashImg = ii.getImage();
		g.drawImage(splashImg, MAIN_WIDTH / 2 - splashImg.getWidth(null) / 2, 
			MAIN_HEIGHT / 2 - splashImg.getHeight(null) / 2, this);
		if (startGame)
			printStringInCenter(g, "Click to start", 250, 18); 
		else printStringInCenter(g, "Click to restart", 250, 18); 
	}
	
	public void paint(Graphics g)
	{
		super.paint(g);
		if (splash) paintSplash(g);
		
		if (countDownNum != 0) //need to count down
		{
			g.setFont(new Font("Arial", Font.ITALIC, 28));
			g.setColor(Color.ORANGE);
			g.drawString("Boss Coming!", 170, 300);
			printStringInCenter(g, ((Integer)countDownNum).toString(), 360, 48);
		}
		
	    if (inGame) {
	        drawEnemies(g);
	        drawPlayer(g);
	        drawMissiles(g, myMissiles);
	        drawBonuses(g);
	        drawExplosions(g);
	        
	        //draw HP
	        g.setColor(Color.RED);
	        hpBar = new HPBar(player.hp);
	        g.fillRoundRect(hpBar.x, hpBar.y, hpBar.width, hpBar.height, 5, 5);
	        Font gameFont = new Font("Helvetica", Font.BOLD, 14);
	        g.setColor(Color.green);
	        g.setFont(gameFont);
	        g.drawString("HP", 441, 600);
	        
	        //draw Score
	        score.paintCurrentScore(g);
	    }
	    else {
	    	if (!startGame) // need to print the result of last game
	    	{
	    		if (bossDefeated)	
	    			printStringInCenter(g, "YOU WIN!", 500, 24);
	    		else printStringInCenter(g, "GAME OVER!", 500, 24);
	    	}
	    	printStringInCenter(g, "Your Score : " + score.getCurrentScore(), 520, 20);
	    	printStringInCenter(g, "Highest Score: " + score.update(), 600, 20);
	    }
	    
	    Toolkit.getDefaultToolkit().sync();
	    g.dispose();
	}
	
    public void drawPlayer(Graphics g) 
    {
        if (player.isVisible()) {
            g.drawImage(player.getImage(), player.getX(), player.getY(), this);
        }
    }
	
    public void drawFlyingObj(Graphics g, FlyingObj obj)
    {	g.drawImage(obj.getImage(), obj.getX(), obj.getY(), this);	}
    
    public void drawMissiles(Graphics g, ArrayList<Missile> missiles)
    {
    	Missile m;
    	int num = missiles.size();
    	for (int i=0; i < num; i++)
    	{
    		try{	m = missiles.get(i);	}
    		catch(IndexOutOfBoundsException e) 	{	break; }
    		drawFlyingObj(g, m);
    	}
    }
    
    public void drawEnemies (Graphics g)
    {
    	BasicEnemy be;
    	int num = enemyGen.easyEnemies.size();
    	int i = 0;
    	while (i < num)
    	{
    		try {	be = enemyGen.easyEnemies.get(i);	}
    		catch(IndexOutOfBoundsException e) {	break; }
    		drawFlyingObj(g, be);
    		i++;
    	}
   		drawMissiles(g, enemyGen.enemyMissiles);
    }
    
    public void drawBonuses (Graphics g)
    {
    	g.setFont(new Font("Helvetica", Font.BOLD, 16));
    	g.setColor(Color.yellow);
    	for (ShowStatusBonus bonus: bonuses)
    	{
    		g.drawString(bonus.message, bonus.getX(), bonus.getY());
    	}
    }
    
    public void drawExplosions (Graphics g)
    {
    	Explosion ex;
    	int num = explosions.size();
    	for (int i=0; i<num; i++)
    	{
    		try {	ex = explosions.get(i);	}
    		catch(IndexOutOfBoundsException e) {	break; }
    		ex = explosions.get(i);
    		ex.paintExplosion(g);
    	}
    }
    
    public void animation()
    {
    	//player
    	player.act();
    	checkPlayerHitEnemy();
    	
    	//statusBonus
    	for (int i = 0; i < bonuses.size(); i++)
    	{
    		ShowStatusBonus bonus = bonuses.get(i);
    		bonus.move();
    		if (bonus.isVisible()) i++;
    		else bonuses.remove(i);
    	}
    	
    	//player's missiles
		moveMissiles(myMissiles);
    	checkMissileHitEnemy(); //check whether any enemy collides with any missile;
    	
    	//enemies
    	int i = 0;
    	while (i < enemyGen.easyEnemies.size())
    	{
    		BasicEnemy be = enemyGen.easyEnemies.get(i);
    		be.act();
    		if (!be.isVisible()) 
    		{
    			if (be instanceof Boss)  //Player wins when the boss dies
    			{
    				bossDefeated = true;
    				score.IncScore(be.maxHP);
    				gameOver();
    			}
    			enemyGen.easyEnemies.remove(i);
    		}
    		else i++;
    	}
    	
    	//enemies' missiles
    	moveMissiles(enemyGen.enemyMissiles);
    	checkMissileHitPlayer();
    	
    }

	private void moveMissiles(ArrayList<Missile> m) {
		int i = 0;
    	while (i < m.size())
    	{
    		Missile mis = m.get(i);
    		mis.move();
    		if (mis.getY() < 0)
    			m.remove(i);
    		else i++;
    	}
	}
    
    public void checkMissileHitEnemy()
    {
    	int i = 0;
    	Missile m;
    	boolean missileDestroyed;
    	BasicEnemy be;
    	while (i < myMissiles.size())
    	{
    		int j = 0;
    		missileDestroyed = false;
    		m = myMissiles.get(i);
	    	while ((j < enemyGen.easyEnemies.size()) && (!missileDestroyed))
	    	{
	    		be = enemyGen.easyEnemies.get(j);
	    		Rectangle myMissileRect = getRectangle(m);
	    		Rectangle enemyRect = getRectangle(be);
	    		if (myMissileRect.intersects(enemyRect))
	    		{
	    			be.beingHit(m.damage);
	    			myMissiles.remove(i);
	    			missileDestroyed = true;
	    			if (!be.isVisible())
	    			{
	    				if (be instanceof StrongEnemy) 
	    					((StrongEnemy)be).stop(); //this enemy stops firing missiles
	    				if (be instanceof SuperEnemy) 
	    					((SuperEnemy)be).stop(); 
	    				enemyDrop(be.dropRate, be.getX(), be.getY());
	    				score.IncScore(be.maxHP);
	    				enemyGen.easyEnemies.remove(j);
	    			}
	    			else j++;  //enemy being hit but not yet destroyed
	    		}
	    		else j++;
	    	}
	    	if (!missileDestroyed) i++;
    	}
    }
    
    private void enemyDrop(double rate, int posX, int posY)
    {
    	if (Math.random() < rate)
    	{
    		if (Math.random()< 0.67)	enemyGen.enemyMissiles.add(new Drop(posX, posY)); 
    		else  enemyGen.enemyMissiles.add(new SpeedDrop(posX, posY, 1));
    	}
    }
    
    private void checkPlayerHitEnemy()
    {
    	int i = 0;
    	BasicEnemy be;
    	Rectangle playerRect = getRectangle(player);
    	while (i < enemyGen.easyEnemies.size())
    	{
    		be = enemyGen.easyEnemies.get(i);
    		Rectangle enemyRect = getRectangle(be);
    		if (playerRect.intersects(enemyRect))
    		{
    			player.beingHit(be.damageHitPlayer);
    			if (! (be instanceof Boss))
    			{
    				enemyGen.easyEnemies.remove(i);
    				score.IncScore(be.hp);
    			}
    			if (!player.isVisible())
    				gameOver();
    		}
    		else i++;
    	}
    }
    
    public void checkMissileHitPlayer()
    {
    	int i = 0;
    	Missile m;
    	Rectangle playerRect = getRectangle(player);
    	while (i < enemyGen.enemyMissiles.size())
    	{
    		m = enemyGen.enemyMissiles.get(i);
    		Rectangle missileRect = getRectangle(m);
    		if (playerRect.intersects(missileRect))
    		{
    			player.beingHit(m.damage);
    			if (m instanceof Drop) // then show bonus
    			{
    				bonuses.add(new ShowStatusBonus(m.getX(), m.getY()+5, ((Drop)m).bonusText));	
    				score.IncScore(2);
    			}
    			if (m instanceof SpeedDrop) 
    			{
    				player.speedUp(((SpeedDrop) m).speedBonus);
    				if (((SpeedDrop)m).speedBonus < 0)  //If hit by missiles of Slowers, score does not increase
    					score.IncScore(-2);
    			}
    			enemyGen.enemyMissiles.remove(i);
    			if (!player.isVisible())
    				gameOver();
    		}
    		else i++;
    	}
    }
    
    public Rectangle getRectangle(FlyingObj obj)  //used for collision checks for all kinds of FlyingObj
    {
    	return (new Rectangle(obj.getX() , obj.getY(), obj.width, obj.height));
    }
    
	public void run() 
	{
	    long beforeTime, timeDiff, sleep;
	    beforeTime = System.currentTimeMillis();

	    while (inGame) {
	        repaint();
	        if (bossDefeated) break;
	        animation();  //control the movements of all the elements on the panel
	        rounds ++; 
	        //System.out.println(player.getX());
	        timeDiff = System.currentTimeMillis() - beforeTime;
	        sleep = DELAY - timeDiff;

	        if (sleep < 0) 
	              sleep = 2;
	        try {
	            Thread.sleep(sleep);
	        } catch (InterruptedException e) {
	            System.out.println("interrupted");
	        }
	        beforeTime = System.currentTimeMillis();
	    }
	    gameOver();
	}
	
	private void printStringInCenter(Graphics g, String string, int height, int fontSize)
	{
		//Graphics g = this.getGraphics();
        Font gameFont = new Font("Helvetica", Font.BOLD, fontSize);
        FontMetrics metr = this.getFontMetrics(gameFont);
        int r = (int)(Math.random()*4);
        switch (r)
        {
        case 0: g.setColor(Color.ORANGE); break;
        case 1: g.setColor(Color.GREEN); break;
        case 2: g.setColor(Color.MAGENTA); break;
        case 3: g.setColor(Color.PINK); break;
        }
        g.setFont(gameFont);
        g.drawString(string, (MAIN_WIDTH - metr.stringWidth(string))/2, height);
	}
	
	public void gameOver()
	{
		inGame = false;
		splash = true;
		start.setVisible(true);
		start.setText("Restart");
		enemyGen.stopThread = true;
		startGame = false; 
		repaint();
	}
	  
    class myKeyAdapter extends KeyAdapter 
    {
        public void keyReleased(KeyEvent e) {
        	if (inGame)
        		player.keyReleased(e);
        }
        public void keyPressed(KeyEvent e) {
        	if (inGame)
        	{
        		player.keyPressed(e);
        		//fire my missiles
        		if ((e.getKeyCode() == KeyEvent.VK_SPACE) && (rounds > 10)){
        			rounds = 0;
        			switch (player.missileState){
        				case 1: fireNormalMissile();
        					break;
        				case 2: fireDoubleMissile();
        					break;
        				case 3: fireTripleMissile();
        					break;
        				case 4: fireSuperMissile();
        			}
        		}
        	}
        }
        
        public void fireNormalMissile()
        {   myMissiles.add(new Missile(player.getX() + player.width/2, player.getY(), 0, true));	}
        
        public void fireDoubleMissile()
        {
        	myMissiles.add(new Missile(player.getX() + player.width/2 - 10, player.getY(), 0, true));
        	myMissiles.add(new Missile(player.getX() + player.width/2 + 10, player.getY(), 0, true));
        }
        
        public void fireTripleMissile()
        {
        	fireNormalMissile();
        	myMissiles.add(new Missile(player.getX() + player.width/2 - 18, player.getY(), 0, true));
        	myMissiles.add(new Missile(player.getX() + player.width/2 + 18, player.getY(), 0, true));
        }
        
        public void fireSuperMissile()
        {   myMissiles.add(new SuperMissile(player.getX() + player.width/2, player.getY(), 0, true));	}
    }
}
